#pragma once
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <ios>
#include <string>
#include <type_traits>
#include <utility>

struct StaticString
{
    constexpr explicit StaticString(const char* str) : data(str)
    {
    }
    const char* data;
};

constexpr auto operator""_s(const char* const s, std::size_t l) -> StaticString
{
    return StaticString(s);
}

namespace std {

inline auto binary(ios_base& base) -> ios_base&
{
    // only a flag
    return base;
}
};  // namespace std

namespace xncc {
enum class LOG_LEVEL : uint8_t
{
    FATAL = 1,
    ERROR = 2,
    WARN  = 3,
    INFO  = 4,
    DEBUG = 5,
};

constexpr auto logLevelToStr(LOG_LEVEL level) -> const char*
{
    return xncc::LOG_LEVEL::DEBUG == level ? "[DBG]" :
           xncc::LOG_LEVEL::INFO == level  ? "[INF]" :
           xncc::LOG_LEVEL::WARN == level  ? "[WAR]" :
           xncc::LOG_LEVEL::ERROR == level ? "[ERR]" :
           xncc::LOG_LEVEL::FATAL == level ? "[FAT]" :
                                             "";
}

class Logger
{
  public:
    explicit Logger(const std::string& logName,
                    LOG_LEVEL          logLevel   = xncc::LOG_LEVEL::DEBUG,
                    const std::string& logPath    = "./",
                    std::size_t        rotateSize = 512,
                    int                coreId     = -1,
                    bool               nameFixed  = false);
    Logger();

    // for log binary
    Logger(const std::string& logName, const std::string& logPath, int coreId = -1);
    ~Logger() = default;

    Logger(const Logger&)                    = delete;
    auto operator=(const Logger&) -> Logger& = delete;
    Logger(Logger&&)                         = delete;
    auto operator=(Logger&&) -> Logger&      = delete;

    static void start();

    void               Set(const std::string& logName,
                           const std::string& logPath,
                           std::size_t        rotateSize,
                           LOG_LEVEL          logLevel,
                           int                coreId,
                           bool               nameFixed = false);
    [[nodiscard]] auto getLevel() const -> LOG_LEVEL
    {
        return logLevel_;
    }

    [[nodiscard]] auto loggerId() const -> uint16_t
    {
        return loggerId_;
    }

  private:
    LOG_LEVEL      logLevel_;
    const uint16_t loggerId_;
    bool           is_init_;
};

using OBJ_LOG_FUNC = void (*)(const void*, std::ostringstream&);

template <class T, class... Args>
auto has_ToString(int) -> decltype(void(std::declval<T>().ToString(std::declval<Args>()...)), std::true_type{});
template <class, class...>
auto has_ToString(...) -> std::false_type;

template <class T, class... Args>
struct has_func_ToString : decltype(has_ToString<T, Args...>(0))
{
};

template <typename Func>
struct is_log_func
    : std::integral_constant<bool, std::is_same<typename std::remove_reference<Func>::type, OBJ_LOG_FUNC>::value>
{
};

// 写一个对象到后台
struct FunctionObjectInnerType
{
    FunctionObjectInnerType(const void* ptr, int dsize, int foffset, int osize)
        : dataPtr(ptr), dataSize(dsize), funcOffset(foffset), objSize(osize)
    {
    }
    const void* dataPtr;
    const int   dataSize;
    const int   funcOffset;
    const int   objSize;
};

template <typename Obj, typename Func>
struct FunctionObject
{
    static_assert(std::is_standard_layout<typename std::remove_reference<Obj>::type>::value,
                  "must be standard_layout type");
    static_assert(std::is_trivially_copyable<typename std::remove_reference<Obj>::type>::value,
                  "must be trivially_copyable type");
    static_assert(std::is_member_function_pointer<Func>::value ? has_func_ToString<Obj, std::ostringstream&>::value :
                                                                 is_log_func<Func>::value,
                  "must has funciton ToString");

    FunctionObject(const Obj& obj, Func f)
        : innerType(&obj, sizeof(Obj), offsetof(struct FunctionObject, func), sizeof(struct FunctionObject)), func(f)
    {
    }
    FunctionObjectInnerType innerType;
    Func                    func;
};

template <typename Obj, typename Func>
constexpr auto FUNCTION_OBJECT(Obj&& obj, Func&& func) -> xncc::FunctionObject<Obj, Func>
{
    return xncc::FunctionObject<Obj, Func>(std::forward<Obj>(obj), std::forward<Func>(func));
}

/*提供的默认log  输出到控制台*/
extern Logger __console_log;
#define CONSOLE_LOG (xncc::__console_log)

class Logbinary
{
  public:
    explicit Logbinary(uint16_t loggerId);
    ~Logbinary();
    Logbinary(const Logbinary&)                    = delete;
    auto operator=(const Logbinary&) -> Logbinary& = delete;
    Logbinary(Logbinary&&)                         = delete;
    auto operator=(Logbinary&&) -> Logbinary&      = delete;

    template <typename Obj>
    auto operator<<(const Obj& arg) -> Logbinary&
    {
        static_assert(std::is_standard_layout<typename std::remove_reference<Obj>::type>::value,
                      "must be standard_layout type");
        static_assert(std::is_trivially_copyable<typename std::remove_reference<Obj>::type>::value,
                      "must be trivially_copyable type");
        constexpr auto entrySize = sizeof(Obj);
        char*          buf       = get(entrySize);
        memcpy(buf, &arg, entrySize);
        extendOffset(entrySize);
        return *this;
    }

  private:
    void ensureLogbufEnough(size_t incLen);
    auto get(std::size_t entrySize) -> char*;
    void extendOffset(std::size_t entrySize);

    uint64_t offset_;
    char*    logBuf_;
    uint64_t curBufLen_;
};

// 定义函数指针类型
using StdIoBaseFlag = std::ios_base& (*)(std::ios_base&);

class LogObject
{
  public:
    LogObject(LOG_LEVEL level, const char* file, uint32_t line, const char* function, uint16_t loggerId);
    explicit LogObject(uint16_t loggerId);

    ~LogObject();

    LogObject(const LogObject&)                    = delete;
    auto operator=(const LogObject&) -> LogObject& = delete;
    LogObject(LogObject&&)                         = delete;
    auto operator=(LogObject&&) -> LogObject&      = delete;

    auto operator<<(bool) -> LogObject&;
    auto operator<<(char) -> LogObject&;
    auto operator<<(StdIoBaseFlag) -> LogObject&;
    auto operator<<(short) -> LogObject&;
    auto operator<<(unsigned short) -> LogObject&;
    auto operator<<(int) -> LogObject&;
    auto operator<<(unsigned int) -> LogObject&;
    auto operator<<(long) -> LogObject&;
    auto operator<<(unsigned long) -> LogObject&;
    auto operator<<(long long) -> LogObject&;
    auto operator<<(unsigned long long) -> LogObject&;

    auto operator<<(const void*) -> LogObject&;
    auto operator<<(float) -> LogObject&;
    auto operator<<(double) -> LogObject&;

    auto operator<<(const char*) -> LogObject&;
    auto operator<<(const unsigned char*) -> LogObject&;
    auto operator<<(StaticString) -> LogObject&;
    auto operator<<(const std::string&) -> LogObject&;
    void operator<<(void (*filePtr)(LogObject&));

    template <typename Obj, typename Func>
    auto operator<<(const FunctionObject<Obj, Func>& arg) -> LogObject&
    {
        constexpr auto entrySize = sizeof(arg) + sizeof(Obj);
        char*          buf       = get(entrySize);
        memcpy(buf, &arg, sizeof(arg));
        memcpy(buf + sizeof(arg), arg.innerType.dataPtr, sizeof(Obj));
        extendOffset(entrySize);
        return *this;
    }
    void flush();

  private:
    template <typename T>
    void encodeNumParam(T data, uint8_t pType);
    void encodeStrParam(const char* str, uint8_t pType, std::size_t len);
    void ensureLogbufEnough(size_t incLen);
    auto get(std::size_t entrySize) -> char*;
    void extendOffset(std::size_t entrySize);
    void addFlag(uint8_t flag);

    uint64_t offset_;
    char*    logBuf_;
    uint64_t curBufLen_;
    bool     isFlushed_;
    bool     isBoolalpha_;
};

void endl(LogObject& fileline);

}  // namespace xncc

// 没有日志头
#define LOG(LOGGER) xncc::LogObject(LOGGER.loggerId())

// 直接写二进制数据
#define LOG_BIN(LOGGER) xncc::Logbinary(LOGGER.loggerId())

#define LOG_DEBUG(LOGGER)                                                                                              \
    if (LOGGER.getLevel() >= xncc::LOG_LEVEL::DEBUG)                                                                   \
    xncc::LogObject(xncc::LOG_LEVEL::DEBUG, __FILE__, __LINE__, __FUNCTION__, LOGGER.loggerId())

#define LOG_INFO(LOGGER)                                                                                               \
    if (LOGGER.getLevel() >= xncc::LOG_LEVEL::INFO)                                                                    \
    xncc::LogObject(xncc::LOG_LEVEL::INFO, __FILE__, __LINE__, __FUNCTION__, LOGGER.loggerId())

#define LOG_WARN(LOGGER)                                                                                               \
    if (LOGGER.getLevel() >= xncc::LOG_LEVEL::WARN)                                                                    \
    xncc::LogObject(xncc::LOG_LEVEL::WARN, __FILE__, __LINE__, __FUNCTION__, LOGGER.loggerId())

#define LOG_ERROR(LOGGER) xncc::LogObject(xncc::LOG_LEVEL::ERROR, __FILE__, __LINE__, __FUNCTION__, LOGGER.loggerId())

#define LOG_FATAL(LOGGER) xncc::LogObject(xncc::LOG_LEVEL::FATAL, __FILE__, __LINE__, __FUNCTION__, LOGGER.loggerId())
